﻿Option Explicit On
Imports System.Xml

Public Class GemSetFinder
    ' Class to sort through a list of candidate sets and, given weights,
    ' find the best set with the best gemming out of all desired sets to check.
    Private items(,) As Item
    Private numItems() As Integer
    Private weights As Weight
    Private race As CalcManager.playerRace
    Private dwor2h As CalcManager.dwor2h
    Private expRacialBonus(0 To 4) As Double
    Private numPieces As Integer
    Private ArPCap As Double
    Private HitCap As Double, SpellCap As Double
    Private ExpCapRating As Double
    Private startID As Double, endID As Double
    Private itemIDs() As Integer
    Private lastIDs() As Integer
    Private totalStats(0 To 8) As Double
    Private preEvalScore As Double
    Private gemMatrix As GemValidityMatrix
    Private gems() As Gem
    Private numGems As Integer
    Private numCandidateSets As Double
    Private candidateSets() As CandidateSet
    Private startIndex As Double, endIndex As Double
    Private bestScore As Double
    Private bestSet As GemSet
    Private bestStats(0 To 8) As Double
    Private curGemSet As GemSet
    Private numGemsInSet() As Integer
    Private forceHit As GemValidityMatrix.forceHitCap
    Private forceExp As Boolean
    Private minHit As Double
    Private minExp As Double
    Private primaryTree As String

    Private curRedGems As Integer, curBlueGems As Integer, curYellowGems As Integer

    Public Event calcFinished(ByVal bestSet As GemSet, ByVal bestStats() As Double, ByVal bestScore As Double)
    Public Event oneSetChecked()

    ' Boolean Variables for internally modeled items.
    ' Items with weird procs or rings / weapons with procs are done internally since
    ' the user interface does not support it.
    Private BrynTrollH As Boolean
    Private BrynTrollN As Boolean
    Private AshenRingE As Boolean
    Private DeathbringersWillH As Boolean
    Private DeathbringersWillN As Boolean
    Private Shadowmourne As Boolean

    Private BrynTrollHValue As Double
    Private BrynTrollNValue As Double
    Private AshenRingEValue As Double = 0
    Private DeathbringersWillHValue As Double
    Private DeathbringersWillHUptime As Double
    Private DeathbringersWillHArP As Boolean
    Private DeathbringersWillNValue As Double
    Private DeathbringersWillNUptime As Double
    Private DeathbringersWillNArP As Boolean
    Private ShadowmourneValue As Double

    Enum stats
        str
        ap
        crit
        haste
        arp
        agi
        hit
        exp
        armor
    End Enum

    Enum slotIdx
        head
        shoulder
        chest
        hand
        leg
        neck
        back
        wrist
        waist
        foot
        finger1
        finger2
        trinket1
        trinket2
        weapon1
        weapon2
    End Enum

    Public Sub setData(ByVal itemsIn(,) As Item, ByVal numItemsIN() As Integer, ByVal weightsIN As Weight, ByVal raceIN As CalcManager.playerRace, _
                       ByVal dwor2hIN As CalcManager.dwor2h, ByVal expRacialBonusIN() As Double, ByVal ArPCapIN As Double, ByVal HitCapIN As Double, _
                       ByVal SpellCapIN As Double, ByVal ExpCapRatingIN As Double, ByVal gemMatrixIN As GemValidityMatrix, ByVal numGemsIN As Integer, ByVal gemsIN() As Gem, ByVal numCandidateSetsIN As Double, _
                       ByVal candidateSetsIN() As CandidateSet, ByVal forcehitIN As GemValidityMatrix.forceHitCap, ByVal forceexpIN As Boolean, ByVal primaryTreeIN As String)
        Try
            ' Get data from our calc manager class that we need to do this.
            Dim loopvar As Integer
            Dim maxNumItems As Integer = 0

            For loopvar = 0 To 12
                If numItemsIN(loopvar) > maxNumItems Then
                    maxNumItems = numItemsIN(loopvar)
                End If
            Next
            ReDim items(0 To 12, 0 To maxNumItems - 1)

            items = itemsIn.Clone
            numItems = numItemsIN.Clone
            weights = weightsIN.Clone
            race = raceIN
            dwor2h = dwor2hIN
            If dwor2h = CalcManager.dwor2h.dualwield Then
                numPieces = 16
            Else
                numPieces = 15
            End If
            expRacialBonus = expRacialBonusIN
            ArPCap = ArPCapIN
            HitCap = HitCapIN
            SpellCap = SpellCapIN
            ExpCapRating = ExpCapRatingIN
            gemMatrix = gemMatrixIN.Clone
            numGems = numGemsIN
            ReDim gems(0 To numGems - 1)
            gems = gemsIN.Clone
            numCandidateSets = numCandidateSetsIN
            ReDim candidateSets(0 To numCandidateSets - 1)
            candidateSets = candidateSetsIN.Clone
            forceHit = forcehitIN
            forceExp = forceexpIN
            primaryTree = primaryTreeIN
            ' Set the miniumum hit and expertise values
            Select Case forceHit
                Case GemValidityMatrix.forceHitCap.none
                    minHit = 0
                Case GemValidityMatrix.forceHitCap.melee
                    minHit = System.Math.Ceiling(HitCap)
                Case GemValidityMatrix.forceHitCap.spell
                    minHit = System.Math.Ceiling(SpellCap)
            End Select
            If forceExp = True Then
                minExp = System.Math.Ceiling(ExpCapRating)
            Else
                minExp = 0
            End If
        Catch ex As Threading.ThreadAbortException
        Catch ex As Exception
            MessageBox.Show("An unexpected error has occurred. DK Optimize will exit.", "DK Optimize", MessageBoxButtons.OK, MessageBoxIcon.Stop, MessageBoxDefaultButton.Button1)
            ErrLogWriter.writeLog(ex)
            End
        End Try
    End Sub

    Public Sub setStart(ByVal startIndexIN As Double, ByVal endIndexIN As Double)
        Try
            startIndex = startIndexIN
            endIndex = endIndexIN
        Catch ex As Threading.ThreadAbortException
        Catch ex As Exception
            MessageBox.Show("An unexpected error has occurred. DK Optimize will exit.", "DK Optimize", MessageBoxButtons.OK, MessageBoxIcon.Stop, MessageBoxDefaultButton.Button1)
            ErrLogWriter.writeLog(ex)
            End
        End Try
    End Sub

    Public Sub findSet()
        Try
            ' The thread starts here.  We just need to iterate over our sets and evaluate them and then pick the best one.
            Dim loopvar As Double, loopvar2 As Double

            ' Set information
            bestScore = 0
            bestSet = New GemSet(numPieces)
            curGemSet = New GemSet(numPieces)
            ReDim numGemsInSet(0 To numGems - 1)

            ' Set up the values for the internally calculated items.
            calculateInternalProcs()

            For loopvar = startIndex To endIndex
                ' Do everything we can pre-gem iteration for the set.
                ' This means add items, expertise bonuses, enchants, etc.

                curGemSet.itemIDs = candidateSets(loopvar).itemIDs.Clone
                curGemSet.wEnch = candidateSets(loopvar).wEnch
                curGemSet.hEnch = candidateSets(loopvar).hEnch
                curGemSet.fench = candidateSets(loopvar).fEnch

                ' Reset stats to zero for the current set.
                preEvalScore = 0
                curRedGems = 0
                curBlueGems = 0
                curYellowGems = 0
                For loopvar2 = 0 To 8
                    totalStats(loopvar2) = 0
                Next
                For loopvar2 = 0 To numGems - 1
                    numGemsInSet(loopvar2) = 0
                Next

                ' Add enchant score for boot / gloves / wrist.
                Select Case curGemSet.wEnch
                    Case CalcManager.Enchants.wrist_Expertise
                        totalStats(stats.exp) += 15
                    Case CalcManager.Enchants.wrist_GreaterAssault
                        totalStats(stats.ap) += 50
                    Case CalcManager.Enchants.wrist_FurLining
                        totalStats(stats.ap) += 130
                End Select

                Select Case curGemSet.hEnch
                    Case CalcManager.Enchants.hand_Crusher
                        totalStats(stats.ap) += 44
                    Case CalcManager.Enchants.hand_HyperspeedAccelerators
                        ' Doesn't affect stats, so these go straight to score.
                        preEvalScore += ((340 * weights.haste) * (12 / 60))
                    Case CalcManager.Enchants.hand_MajorStrength
                        totalStats(stats.str) += 15
                    Case CalcManager.Enchants.hand_Precision
                        totalStats(stats.hit) += 20
                End Select

                Select Case curGemSet.fench
                    Case CalcManager.Enchants.foot_GreaterAssault
                        totalStats(stats.ap) += 32
                    Case CalcManager.Enchants.foot_Icewalker
                        totalStats(stats.hit) += 12 : totalStats(stats.crit) += 12
                    Case CalcManager.Enchants.foot_NitroBoosts
                        totalStats(stats.crit) += 24
                End Select

                ' Score for Tier bonuses.  Straight into preEvalScore
                preEvalScore += setBonusScore()

                ' Expertise from weapon racial.  Just the stat, not the score.
                totalStats(stats.exp) += expRacialBonus(items(12, curGemSet.itemIDs(slotIdx.weapon1)).weptype)

                ' Speed value of weapons.  There is no stat for this, so just put it straight to score.
                If numPieces = 16 Then
                    preEvalScore += (items(12, curGemSet.itemIDs(slotIdx.weapon1)).wepspeed * weights.mhspeed)
                    preEvalScore += (items(12, curGemSet.itemIDs(slotIdx.weapon2)).wepspeed * weights.ohspeed)
                Else
                    preEvalScore += (items(12, curGemSet.itemIDs(slotIdx.weapon1)).wepspeed * weights.mhspeed)
                End If

                ' DPS Value of weapons.  There is also no stat for this, so it just goes straight to score.
                If numPieces = 16 Then
                    preEvalScore += (items(12, curGemSet.itemIDs(slotIdx.weapon1)).wepdps * weights.mhdps)
                    preEvalScore += (items(12, curGemSet.itemIDs(slotIdx.weapon2)).wepdps * weights.ohdps)
                Else
                    preEvalScore += (items(12, curGemSet.itemIDs(slotIdx.weapon1)).wepdps * weights.mhdps)
                End If

                ' Base stats from items.  totalstats was already zeroed above.
                For loopvar2 = 0 To numPieces - 1
                    totalStats(stats.str) += items(trans(loopvar2), curGemSet.itemIDs(loopvar2)).str
                    totalStats(stats.ap) += items(trans(loopvar2), curGemSet.itemIDs(loopvar2)).ap
                    totalStats(stats.crit) += items(trans(loopvar2), curGemSet.itemIDs(loopvar2)).crit
                    totalStats(stats.haste) += items(trans(loopvar2), curGemSet.itemIDs(loopvar2)).haste
                    totalStats(stats.arp) += items(trans(loopvar2), curGemSet.itemIDs(loopvar2)).arp
                    totalStats(stats.agi) += items(trans(loopvar2), curGemSet.itemIDs(loopvar2)).agi
                    totalStats(stats.hit) += items(trans(loopvar2), curGemSet.itemIDs(loopvar2)).hit
                    totalStats(stats.exp) += items(trans(loopvar2), curGemSet.itemIDs(loopvar2)).exp
                    totalStats(stats.armor) += items(trans(loopvar2), curGemSet.itemIDs(loopvar2)).armor
                Next

                ' Internally modeled items.  Add score for all non-cappable stats
                ' for any active internally modeled items.  Set booleans
                ' so that the evalulation function will know what is active later.
                If Left$(items(trans(slotIdx.finger1), curGemSet.itemIDs(slotIdx.finger1)).name, 21) = "Ashen Band of Endless" Or _
                   Left$(items(trans(slotIdx.finger2), curGemSet.itemIDs(slotIdx.finger2)).name, 21) = "Ashen Band of Endless" Or _
                   Left$(items(trans(slotIdx.finger1), curGemSet.itemIDs(slotIdx.finger1)).name, 23) = "Ashen Band of Unmatched" Or _
                   Left$(items(trans(slotIdx.finger1), curGemSet.itemIDs(slotIdx.finger1)).name, 23) = "Ashen Band of Unmatched" Then

                    preEvalScore += AshenRingEValue
                    AshenRingE = True

                Else
                    AshenRingE = False
                End If

                If items(trans(slotIdx.trinket1), curGemSet.itemIDs(slotIdx.trinket1)).name = "Deathbringer's Will (Heroic)" Or _
                   items(trans(slotIdx.trinket2), curGemSet.itemIDs(slotIdx.trinket2)).name = "Deathbringer's Will (Heroic)" Then
                    preEvalScore += DeathbringersWillHValue
                    DeathbringersWillH = True
                Else
                    DeathbringersWillH = False
                End If

                If items(trans(slotIdx.trinket1), curGemSet.itemIDs(slotIdx.trinket1)).name = "Deathbringer's Will" Or _
                   items(trans(slotIdx.trinket2), curGemSet.itemIDs(slotIdx.trinket2)).name = "Deathbringer's Will" Then
                    preEvalScore += DeathbringersWillNValue
                    DeathbringersWillN = True
                Else
                    DeathbringersWillN = False
                End If

                If items(trans(slotIdx.weapon1), curGemSet.itemIDs(slotIdx.weapon1)).name = "Bryntroll, the Bone Arbiter (Heroic)" Then
                    preEvalScore += BrynTrollHValue
                    BrynTrollH = True
                Else
                    BrynTrollH = False
                End If

                If items(trans(slotIdx.weapon1), curGemSet.itemIDs(slotIdx.weapon1)).name = "Bryntroll, the Bone Arbiter" Then
                    preEvalScore += BrynTrollNValue
                    BrynTrollN = True
                Else
                    BrynTrollN = False
                End If

                If items(trans(slotIdx.weapon1), curGemSet.itemIDs(slotIdx.weapon1)).name = "Shadowmourne" Then
                    preEvalScore += ShadowmourneValue
                    Shadowmourne = True
                Else
                    Shadowmourne = False
                End If

                ' Call our iteration.  This will update bestscore etc.
                IterateSets(slotIdx.head)
                RaiseEvent oneSetChecked()
            Next

            ' We're done iterating.
            ' bestSet, bestStats, and bestScore contain, well, those things.  send 'em back.
            RaiseEvent calcFinished(bestSet, bestStats, bestScore)

            ' Nothing left to do.
        Catch ex As Threading.ThreadAbortException
        Catch ex As Exception
            MessageBox.Show("An unexpected error has occurred. DK Optimize will exit.", "DK Optimize", MessageBoxButtons.OK, MessageBoxIcon.Stop, MessageBoxDefaultButton.Button1)
            ErrLogWriter.writeLog(ex)
            End
        End Try
    End Sub

    Private Function setBonusScore() As Double
        Try
            Dim setString(0 To 3) As String
            Dim loopvar As Integer, loopvar2 As Integer
            Dim numFound(0 To 3) As Integer
            Dim Score As Double

            setString(0) = "Scourgeborne"
            setString(1) = "Darkruned"
            setString(2) = "Koltira's"
            setString(3) = "Scourgelord "

            For loopvar = 0 To 3
                numFound(loopvar) = 0
                For loopvar2 = slotIdx.head To slotIdx.leg
                    If InStr(items(loopvar2, curGemSet.itemIDs(loopvar2)).name, setString(loopvar)) <> 0 Then numFound(loopvar) += 1
                Next
            Next

            Score = 0
            If numFound(0) >= 4 Then Score += weights.tier(0, 1)
            If numFound(0) >= 2 Then Score += weights.tier(0, 0)
            If numFound(1) >= 4 Then Score += weights.tier(1, 1)
            If numFound(1) >= 2 Then Score += weights.tier(1, 0)
            If numFound(2) >= 4 Then Score += weights.tier(2, 1)
            If numFound(2) >= 2 Then Score += weights.tier(2, 0)
            If numFound(3) >= 4 Then Score += weights.tier(3, 1)
            If numFound(3) >= 2 Then Score += weights.tier(3, 0)

            setBonusScore = Score
        Catch ex As Threading.ThreadAbortException
        Catch ex As Exception
            MessageBox.Show("An unexpected error has occurred. DK Optimize will exit.", "DK Optimize", MessageBoxButtons.OK, MessageBoxIcon.Stop, MessageBoxDefaultButton.Button1)
            ErrLogWriter.writeLog(ex)
            End
        End Try
    End Function

    Private Function trans(ByVal slotindex As Integer) As Integer
        Try
            ' Takes a slotidx value and returns an items() index.
            ' useful!

            If slotindex > slotIdx.foot Then
                If slotindex = slotIdx.finger1 Or slotindex = slotIdx.finger2 Then
                    trans = 10
                ElseIf slotindex = slotIdx.trinket1 Or slotindex = slotIdx.trinket2 Then
                    trans = 11
                ElseIf slotindex = slotIdx.weapon1 Or slotindex = slotIdx.weapon2 Then
                    trans = 12
                End If
            Else
                trans = slotindex
            End If
        Catch ex As Threading.ThreadAbortException
        Catch ex As Exception
            MessageBox.Show("An unexpected error has occurred. DK Optimize will exit.", "DK Optimize", MessageBoxButtons.OK, MessageBoxIcon.Stop, MessageBoxDefaultButton.Button1)
            ErrLogWriter.writeLog(ex)
            End
        End Try
    End Function

    Private Function numSockets(ByVal inItem As Item) As Integer
        Try
            ' Returns the number of sockets an item has.
            If inItem.socketcolor(2) <> Item.colors.none Then
                numSockets = 3
            ElseIf inItem.socketcolor(1) <> Item.colors.none Then
                numSockets = 2
            ElseIf inItem.socketcolor(0) <> Item.colors.none Then
                numSockets = 1
            Else
                numSockets = 0
            End If
        Catch ex As Threading.ThreadAbortException
        Catch ex As Exception
            MessageBox.Show("An unexpected error has occurred. DK Optimize will exit.", "DK Optimize", MessageBoxButtons.OK, MessageBoxIcon.Stop, MessageBoxDefaultButton.Button1)
            ErrLogWriter.writeLog(ex)
            End
        End Try
    End Function

    Public Sub IterateSets(ByVal slotIndex As slotIdx)
        Try
            ' Loop through our pieces recursively and add gems that match our validity matrix when we are able to.
            ' Cool stuff, but very time consuming.  Validity for gems has been pre-calculated and is stored in our validity
            ' matrix.

            Dim loopvar As Long, loopvar2 As Long, loopvar3 As Long

            Select Case numSockets(items(trans(slotIndex), curGemSet.itemIDs(slotIndex)))
                Case 0
                    For loopvar = 0 To 2
                        curGemSet.clearGem(slotIndex, loopvar)
                    Next
                    ' No gems here, so move to the next piece.
                    ' Or, if we're at the last piece, evaluate.
                    addGemScore(slotIndex)
                    If slotIndex < numPieces - 1 Then
                        IterateSets(slotIndex + 1)
                    Else
                        evalSet()
                    End If
                    subtractGemScore(slotIndex)
                Case 1
                    For loopvar = 1 To 2
                        curGemSet.clearGem(slotIndex, loopvar)
                    Next
                    ' Now loop through all gems for the first socket for only valid gems.
                    For loopvar = 0 To numGems - 1
                        If gemMatrix.fitsStrict(loopvar, items(trans(slotIndex), curGemSet.itemIDs(slotIndex)).socketcolor(0), totalStats(stats.hit), totalStats(stats.exp)) = True Then
                            ' The gem is valid.  If it's not it'll just loop.  Now we have to make sure we can add this gem to the group
                            ' I.E. it is not a Nightmare tear.
                            If canAddGem(loopvar) = True Then
                                ' Now we can actually add this gem.  We increment our count of the number
                                ' of this gem in the set and then actually add the gem.
                                numGemsInSet(loopvar) += 1
                                curGemSet.gemIDs(slotIndex, 0) = loopvar
                                addGemScore(slotIndex)
                                If slotIndex < numPieces - 1 Then
                                    IterateSets(slotIndex + 1)
                                Else
                                    evalSet()
                                End If
                                subtractGemScore(slotIndex)
                                numGemsInSet(loopvar) -= 1
                            End If
                        End If
                    Next
                Case 2
                    ' Nested logic for two sockets is the same as one socket.
                    ' Shouldn't be tooooo overly terrible.
                    curGemSet.clearGem(slotIndex, 2)
                    ' Now loop through all gems for the first socket for only valid gems.
                    For loopvar = 0 To numGems - 1
                        If gemMatrix.fitsStrict(loopvar, items(trans(slotIndex), curGemSet.itemIDs(slotIndex)).socketcolor(0), totalStats(stats.hit), totalStats(stats.exp)) = True Then
                            If canAddGem(loopvar) = True Then
                                numGemsInSet(loopvar) += 1
                                curGemSet.gemIDs(slotIndex, 0) = loopvar
                                For loopvar2 = 0 To numGems - 1
                                    If gemMatrix.fitsStrict(loopvar2, items(trans(slotIndex), curGemSet.itemIDs(slotIndex)).socketcolor(1), totalStats(stats.hit), totalStats(stats.exp)) = True Then
                                        If canAddGem(loopvar2) = True Then
                                            numGemsInSet(loopvar2) += 1
                                            curGemSet.gemIDs(slotIndex, 1) = loopvar2
                                            addGemScore(slotIndex)
                                            If slotIndex < numPieces - 1 Then
                                                IterateSets(slotIndex + 1)
                                            Else
                                                evalSet()
                                            End If
                                            subtractGemScore(slotIndex)
                                            numGemsInSet(loopvar2) -= 1
                                        End If
                                    End If
                                Next
                                numGemsInSet(loopvar) -= 1
                            End If
                        End If
                    Next
                Case 3
                    ' Now loop through all gems for the first socket for only valid gems.
                    For loopvar = 0 To numGems - 1
                        If gemMatrix.fitsStrict(loopvar, items(trans(slotIndex), curGemSet.itemIDs(slotIndex)).socketcolor(0), totalStats(stats.hit), totalStats(stats.exp)) = True Then
                            If canAddGem(loopvar) = True Then
                                numGemsInSet(loopvar) += 1
                                curGemSet.gemIDs(slotIndex, 0) = loopvar
                                For loopvar2 = 0 To numGems - 1
                                    If gemMatrix.fitsStrict(loopvar2, items(trans(slotIndex), curGemSet.itemIDs(slotIndex)).socketcolor(1), totalStats(stats.hit), totalStats(stats.exp)) = True Then
                                        If canAddGem(loopvar2) = True Then
                                            numGemsInSet(loopvar2) += 1
                                            curGemSet.gemIDs(slotIndex, 1) = loopvar2
                                            For loopvar3 = 0 To numGems - 1
                                                If gemMatrix.fitsStrict(loopvar3, items(trans(slotIndex), curGemSet.itemIDs(slotIndex)).socketcolor(2), totalStats(stats.hit), totalStats(stats.exp)) = True Then
                                                    If canAddGem(loopvar3) = True Then
                                                        numGemsInSet(loopvar3) += 1
                                                        curGemSet.gemIDs(slotIndex, 2) = loopvar3
                                                        addGemScore(slotIndex)
                                                        If slotIndex < numPieces - 1 Then
                                                            IterateSets(slotIndex + 1)
                                                        Else
                                                            evalSet()
                                                        End If
                                                        subtractGemScore(slotIndex)
                                                        numGemsInSet(loopvar3) -= 1
                                                    End If
                                                End If
                                            Next
                                            numGemsInSet(loopvar2) -= 1
                                        End If
                                    End If
                                Next
                                numGemsInSet(loopvar) -= 1
                            End If
                        End If
                    Next
            End Select
        Catch ex As Threading.ThreadAbortException
        Catch ex As Exception
            MessageBox.Show("An unexpected error has occurred. DK Optimize will exit.", "DK Optimize", MessageBoxButtons.OK, MessageBoxIcon.Stop, MessageBoxDefaultButton.Button1)
            ErrLogWriter.writeLog(ex)
            End
        End Try

    End Sub

    Private Sub addGemScore(ByVal slotIndex As Integer)
        Try
            ' Gem_ChangeScore has been pulled out tino addGemScore and subtractGemScore
            ' in this version for ease of reading.
            ' This will add score, socket bonus, etc.
            ' for the given slot.

            ' IMPORTANT - it also increments the gem count for colors used as needed,
            ' which is important for meta gem stuff later.

            Dim loopvar As Integer
            Dim doSetBonus As Boolean
            Dim tempNumSockets As Integer

            tempNumSockets = numSockets(items(trans(slotIndex), curGemSet.itemIDs(slotIndex)))

            For loopvar = 0 To tempNumSockets - 1
                totalStats(stats.agi) += gems(curGemSet.gemIDs(slotIndex, loopvar)).agi
                totalStats(stats.ap) += gems(curGemSet.gemIDs(slotIndex, loopvar)).ap
                totalStats(stats.arp) += gems(curGemSet.gemIDs(slotIndex, loopvar)).arp
                totalStats(stats.crit) += gems(curGemSet.gemIDs(slotIndex, loopvar)).crit
                totalStats(stats.exp) += gems(curGemSet.gemIDs(slotIndex, loopvar)).exp
                totalStats(stats.haste) += gems(curGemSet.gemIDs(slotIndex, loopvar)).haste
                totalStats(stats.hit) += gems(curGemSet.gemIDs(slotIndex, loopvar)).hit
                totalStats(stats.str) += gems(curGemSet.gemIDs(slotIndex, loopvar)).str
            Next

            ' Now increment our gemsinset counts
            For loopvar = 0 To tempNumSockets - 1
                Select Case gems(curGemSet.gemIDs(slotIndex, loopvar)).color
                    Case Gem.colors.red
                        curRedGems += 1
                    Case Gem.colors.yellow
                        curYellowGems += 1
                    Case Gem.colors.blue
                        curBlueGems += 1
                    Case Gem.colors.orange
                        curRedGems += 1 : curYellowGems += 1
                    Case Gem.colors.green
                        curYellowGems += 1 : curBlueGems += 1
                    Case Gem.colors.purple
                        curRedGems += 1 : curBlueGems += 1
                    Case Gem.colors.prismatic
                        curRedGems += 1 : curYellowGems += 1 : curBlueGems += 1
                End Select
            Next

            ' Socket Bonus time
            doSetBonus = True
            For loopvar = 0 To tempNumSockets - 1
                If socketMatch(gems(curGemSet.gemIDs(slotIndex, loopvar)).color, items(trans(slotIndex), curGemSet.itemIDs(slotIndex)).socketcolor(loopvar)) = False Then
                    doSetBonus = False
                End If
            Next

            If doSetBonus = True Then
                Select Case items(trans(slotIndex), curGemSet.itemIDs(slotIndex)).socketstat
                    Case Item.stats.agi
                        totalStats(stats.agi) += items(trans(slotIndex), curGemSet.itemIDs(slotIndex)).socketbonus
                    Case Item.stats.ap
                        totalStats(stats.ap) += items(trans(slotIndex), curGemSet.itemIDs(slotIndex)).socketbonus
                    Case Item.stats.arp
                        totalStats(stats.arp) += items(trans(slotIndex), curGemSet.itemIDs(slotIndex)).socketbonus
                    Case Item.stats.crit
                        totalStats(stats.crit) += items(trans(slotIndex), curGemSet.itemIDs(slotIndex)).socketbonus
                    Case Item.stats.exp
                        totalStats(stats.exp) += items(trans(slotIndex), curGemSet.itemIDs(slotIndex)).socketbonus
                    Case Item.stats.haste
                        totalStats(stats.haste) += items(trans(slotIndex), curGemSet.itemIDs(slotIndex)).socketbonus
                    Case Item.stats.hit
                        totalStats(stats.hit) += items(trans(slotIndex), curGemSet.itemIDs(slotIndex)).socketbonus
                    Case Item.stats.str
                        totalStats(stats.str) += items(trans(slotIndex), curGemSet.itemIDs(slotIndex)).socketbonus
                    Case Item.stats.none
                        ' Do nothing
                End Select
            End If
        Catch ex As Threading.ThreadAbortException
        Catch ex As Exception
            MessageBox.Show("An unexpected error has occurred. DK Optimize will exit.", "DK Optimize", MessageBoxButtons.OK, MessageBoxIcon.Stop, MessageBoxDefaultButton.Button1)
            ErrLogWriter.writeLog(ex)
            End
        End Try
    End Sub

    Private Sub subtractGemScore(ByVal slotIndex As Integer)
        ' Gem_ChangeScore has been pulled out tino addGemScore and subtractGemScore
        ' in this version for ease of reading.
        ' This will add score, socket bonus, etc.
        ' for the given slot.

        ' IMPORTANT - it also increments the gem count for colors used as needed,
        ' which is important for meta gem stuff later.
        Try
            Dim loopvar As Integer
            Dim doSetBonus As Boolean
            Dim tempNumSockets As Integer

            tempNumSockets = numSockets(items(trans(slotIndex), curGemSet.itemIDs(slotIndex)))

            For loopvar = 0 To tempNumSockets - 1
                totalStats(stats.agi) -= gems(curGemSet.gemIDs(slotIndex, loopvar)).agi
                totalStats(stats.ap) -= gems(curGemSet.gemIDs(slotIndex, loopvar)).ap
                totalStats(stats.arp) -= gems(curGemSet.gemIDs(slotIndex, loopvar)).arp
                totalStats(stats.crit) -= gems(curGemSet.gemIDs(slotIndex, loopvar)).crit
                totalStats(stats.exp) -= gems(curGemSet.gemIDs(slotIndex, loopvar)).exp
                totalStats(stats.haste) -= gems(curGemSet.gemIDs(slotIndex, loopvar)).haste
                totalStats(stats.hit) -= gems(curGemSet.gemIDs(slotIndex, loopvar)).hit
                totalStats(stats.str) -= gems(curGemSet.gemIDs(slotIndex, loopvar)).str
            Next

            ' Now decrement our gemsinset counts
            For loopvar = 0 To tempNumSockets - 1
                Select Case gems(curGemSet.gemIDs(slotIndex, loopvar)).color
                    Case Gem.colors.red
                        curRedGems -= 1
                    Case Gem.colors.yellow
                        curYellowGems -= 1
                    Case Gem.colors.blue
                        curBlueGems -= 1
                    Case Gem.colors.orange
                        curRedGems -= 1 : curYellowGems -= 1
                    Case Gem.colors.green
                        curYellowGems -= 1 : curBlueGems -= 1
                    Case Gem.colors.purple
                        curRedGems -= 1 : curBlueGems -= 1
                    Case Gem.colors.prismatic
                        curRedGems -= 1 : curYellowGems -= 1 : curBlueGems -= 1
                End Select
            Next

            ' Socket Bonus time
            doSetBonus = True
            For loopvar = 0 To tempNumSockets - 1
                If socketMatch(gems(curGemSet.gemIDs(slotIndex, loopvar)).color, items(trans(slotIndex), curGemSet.itemIDs(slotIndex)).socketcolor(loopvar)) = False Then
                    doSetBonus = False
                End If
            Next

            If doSetBonus = True Then
                Select Case items(trans(slotIndex), curGemSet.itemIDs(slotIndex)).socketstat
                    Case Item.stats.agi
                        totalStats(stats.agi) -= items(trans(slotIndex), curGemSet.itemIDs(slotIndex)).socketbonus
                    Case Item.stats.ap
                        totalStats(stats.ap) -= items(trans(slotIndex), curGemSet.itemIDs(slotIndex)).socketbonus
                    Case Item.stats.arp
                        totalStats(stats.arp) -= items(trans(slotIndex), curGemSet.itemIDs(slotIndex)).socketbonus
                    Case Item.stats.crit
                        totalStats(stats.crit) -= items(trans(slotIndex), curGemSet.itemIDs(slotIndex)).socketbonus
                    Case Item.stats.exp
                        totalStats(stats.exp) -= items(trans(slotIndex), curGemSet.itemIDs(slotIndex)).socketbonus
                    Case Item.stats.haste
                        totalStats(stats.haste) -= items(trans(slotIndex), curGemSet.itemIDs(slotIndex)).socketbonus
                    Case Item.stats.hit
                        totalStats(stats.hit) -= items(trans(slotIndex), curGemSet.itemIDs(slotIndex)).socketbonus
                    Case Item.stats.str
                        totalStats(stats.str) -= items(trans(slotIndex), curGemSet.itemIDs(slotIndex)).socketbonus
                    Case Item.stats.none
                        ' Do nothing
                End Select
            End If
        Catch ex As Threading.ThreadAbortException
        Catch ex As Exception
            MessageBox.Show("An unexpected error has occurred. DK Optimize will exit.", "DK Optimize", MessageBoxButtons.OK, MessageBoxIcon.Stop, MessageBoxDefaultButton.Button1)
            ErrLogWriter.writeLog(ex)
            End
        End Try
    End Sub

    Private Sub evalSet()
        ' We have all of our stats in totalstats
        ' and preevalscore contains most of the stuff we need to score
        ' that doesn't change from gem iteration to the next.
        ' What we DO NOT need to handle:
        '  stats from gear
        '  stats from gems
        '  stats from enchants
        '  socket bonuses
        '  weapon dps / speed values
        '  Tier set bonuses
        '  weapon racial expertise bonuses

        ' What we DO need to handle:
        '  meta gem calculations
        '  scores for the various stats
        '  scores for our trinket procs since they are cap-dependent

        ' After all that, do the 'is this better' dance and we'll find our best set.
        Try
            Dim myTotalStats(0 To 8) As Double

            ' Copy stats into local so we don't futz it up.
            myTotalStats = totalStats.Clone

            ' Meta Gem
            If weights.crit * 21 > weights.agi * 21 Then
                ' Ideally CSD here
                If curBlueGems >= 2 Then
                    curGemSet.metaGem = GemSet.metaGems.csd
                ElseIf curBlueGems >= 1 And curRedGems >= 1 And curYellowGems >= 1 Then
                    curGemSet.metaGem = GemSet.metaGems.red
                Else
                    ' No valid combination
                    Exit Sub
                End If
            Else
                ' Ideally RED here
                If curBlueGems >= 1 And curYellowGems >= 1 And curRedGems >= 1 Then
                    curGemSet.metaGem = GemSet.metaGems.red
                ElseIf curBlueGems >= 2 Then
                    curGemSet.metaGem = GemSet.metaGems.csd
                Else
                    ' No valid combination
                    Exit Sub
                End If
            End If

            ' Now add the stats for the selected meta gem
            If curGemSet.metaGem = GemSet.metaGems.csd Then
                myTotalStats(stats.crit) += 21
            Else
                myTotalStats(stats.agi) += 21
            End If

            ' Now we begin building our set score.
            Dim myTotalScore As Double
            myTotalScore = preEvalScore

            ' Hit is difficult, as usual.
            If totalStats(stats.hit) > HitCap Then
                If totalStats(stats.hit) > SpellCap Then
                    myTotalScore += ((HitCap * weights.hit) + ((SpellCap - HitCap) * weights.sphit) + ((myTotalStats(stats.hit) - SpellCap) * weights.exhit))
                Else
                    myTotalScore += ((HitCap * weights.hit) + ((myTotalStats(stats.hit) - HitCap) * weights.sphit))
                End If
            Else
                myTotalScore += (myTotalStats(stats.hit) * weights.hit)
            End If

            ' Expertise rating
            If myTotalStats(stats.exp) > ExpCapRating Then
                myTotalScore += (ExpCapRating * weights.exp)
            Else
                myTotalScore += (myTotalStats(stats.exp) * weights.exp)
            End If

            ' Armor Penetration Rating
            If myTotalStats(stats.arp) > ArPCap Then
                myTotalScore += (ArPCap * weights.arp)
            Else
                myTotalScore += (myTotalStats(stats.arp) * weights.arp)
            End If

            ' Add the remainder of the stats.
            myTotalScore += ((myTotalStats(stats.str) * weights.str) + _
                             (myTotalStats(stats.ap) * weights.ap) + _
                             (myTotalStats(stats.crit) * weights.crit) + _
                             (myTotalStats(stats.haste) * weights.haste) + _
                             (myTotalStats(stats.agi) * weights.agi) + _
                             (myTotalStats(stats.armor) * weights.armor))

            ' Internally modeled items that have cappable stats.
            ' as of documentation this only includes (possibly) Deathbringer's Will
            ' as it may or may not proc ArP.
            If DeathbringersWillH = True And DeathbringersWillHArP = True Then
                If myTotalStats(stats.arp) + 700 > ArPCap Then
                    myTotalScore += ((ArPCap - myTotalStats(stats.arp)) * 0.1 * weights.arp)
                Else
                    myTotalScore += (700 * 0.1 * weights.arp)
                End If
            End If
            If DeathbringersWillN = True And DeathbringersWillNArP = True Then
                If myTotalStats(stats.arp) + 600 > ArPCap Then
                    myTotalScore += ((ArPCap - myTotalStats(stats.arp)) * 0.1 * weights.arp)
                Else
                    myTotalScore += (600 * 0.1 * weights.arp)
                End If
            End If

            ' Alright, we know our value for everything except the trinket proc,
            ' and we know exactly how much ARP, HIT, and EXP we have for everything
            ' except the proc.  Now it's time for the trinket stuff.
            ' Trinket procs, which is kind of ugly.
            ' The goal is to only count the partial value of the trinkets if
            ' the procs put us over the arp, exp, or hit cap.
            For loopvar = slotIdx.trinket1 To slotIdx.trinket2
                With items(11, curGemSet.itemIDs(loopvar))
                    Select Case .procstat
                        Case Item.stats.str
                            myTotalScore += (.procvalue * .procuptime * weights.str)
                        Case Item.stats.ap
                            myTotalScore += (.procvalue * .procuptime * weights.ap)
                        Case Item.stats.crit
                            myTotalScore += (.procvalue * .procuptime * weights.crit)
                        Case Item.stats.haste
                            myTotalScore += (.procvalue * .procuptime * weights.haste)
                        Case Item.stats.arp
                            If myTotalStats(stats.arp) + .procvalue > ArPCap Then
                                myTotalScore += ((ArPCap - myTotalStats(stats.arp)) * .procuptime * weights.arp)
                            Else
                                myTotalScore += (.procvalue * .procuptime * weights.arp)
                            End If
                        Case Item.stats.agi
                            myTotalScore += (.procvalue * .procuptime * weights.agi)
                        Case Item.stats.hit
                            If myTotalStats(stats.hit) < HitCap Then
                                If myTotalStats(stats.hit) + .procvalue <= HitCap Then
                                    myTotalScore += (.procvalue * .procuptime * weights.hit)
                                Else
                                    If myTotalStats(stats.hit) + .procvalue <= SpellCap Then
                                        myTotalScore += ((HitCap - myTotalStats(stats.hit)) * .procuptime * weights.hit) + ((myTotalStats(stats.hit) + .procvalue - HitCap) * .procuptime * weights.sphit)
                                    Else
                                        myTotalScore += ((myTotalStats(stats.hit) + .procvalue - SpellCap) * .procuptime * weights.exhit)
                                        myTotalScore += ((SpellCap - HitCap) * .procuptime * weights.sphit)
                                        myTotalScore += ((HitCap - myTotalStats(stats.hit)) * .procuptime * weights.hit)
                                    End If
                                End If
                            ElseIf myTotalStats(stats.hit) < SpellCap Then
                                If myTotalStats(stats.hit) + .procvalue < SpellCap Then
                                    myTotalScore += .procvalue * .procuptime * weights.sphit
                                Else
                                    myTotalScore += ((SpellCap - myTotalStats(stats.hit)) * .procuptime * weights.sphit)
                                    myTotalScore += ((myTotalStats(stats.hit) + .procvalue - SpellCap) * .procuptime * weights.exhit)
                                End If
                            Else
                                myTotalScore += .procvalue * .procuptime * weights.exhit
                            End If
                        Case Item.stats.exp
                            If myTotalStats(stats.exp) + .procvalue > ExpCapRating Then
                                myTotalScore += ((ExpCapRating - myTotalStats(stats.exp)) * .procuptime * weights.exp)
                            Else
                                myTotalScore += (.procvalue * .procuptime * weights.exp)
                            End If
                    End Select
                End With
            Next

            ' Alright, myTotalStats and myTotalScore contain the final results
            ' with everything included except for non-variable enchants.
            ' As a note, expertise rating at this point inclues the rating
            ' added as a result of weapon racial.  It will need to be pulled
            ' out when displaying final results to make things look right.

            ' Now if I'm better than the best, store me.
            If myTotalStats(stats.hit) >= minHit And myTotalStats(stats.exp) >= minExp Then
                If myTotalScore > bestScore Then
                    bestSet = curGemSet.Clone
                    bestScore = myTotalScore
                    bestStats = myTotalStats.Clone
                End If
            End If
        Catch ex As Threading.ThreadAbortException
        Catch ex As Exception
            MessageBox.Show("An unexpected error has occurred. DK Optimize will exit.", "DK Optimize", MessageBoxButtons.OK, MessageBoxIcon.Stop, MessageBoxDefaultButton.Button1)
            ErrLogWriter.writeLog(ex)
            End
        End Try
    End Sub

    Private Function canAddGem(ByVal gemIndex) As Boolean
        ' Returns true if we can add another gem of the specified index
        ' to the set.  Will only ever return false for JC gems and
        ' for nightmare tear / unique gems.
        Try
            Dim loopvar As Integer
            Dim numJCSocketed As Integer

            If gems(gemIndex).unique = False And gems(gemIndex).jcgem = False Then
                canAddGem = True
                Exit Function
            Else
                ' Possible conflict.
                If gems(gemIndex).unique = True Then
                    If numGemsInSet(gemIndex) = 0 Then
                        canAddGem = True
                        Exit Function
                    Else
                        canAddGem = False
                        Exit Function
                    End If
                End If
                If gems(gemIndex).jcgem = True Then
                    numJCSocketed = 0
                    For loopvar = 0 To numGems - 1
                        If gems(loopvar).jcgem = True Then
                            numJCSocketed += numGemsInSet(loopvar)
                        End If
                    Next
                    If numJCSocketed < 3 Then
                        canAddGem = True
                        Exit Function
                    Else
                        canAddGem = False
                        Exit Function
                    End If
                End If
            End If
        Catch ex As Threading.ThreadAbortException
        Catch ex As Exception
            MessageBox.Show("An unexpected error has occurred. DK Optimize will exit.", "DK Optimize", MessageBoxButtons.OK, MessageBoxIcon.Stop, MessageBoxDefaultButton.Button1)
            ErrLogWriter.writeLog(ex)
            End
        End Try
    End Function

    Private Function socketMatch(ByVal gemColor As Gem.colors, ByVal socketColor As Item.colors) As Boolean
        ' Returns a boolean representing whether or not the given gem color would count towards socket bonus
        ' for the given socket color.
        Try
            Select Case socketColor
                Case Item.colors.red
                    If gemColor = Gem.colors.red Or gemColor = Gem.colors.orange Or gemColor = Gem.colors.purple Or gemColor = Gem.colors.prismatic Then
                        socketMatch = True
                    End If
                Case Item.colors.yellow
                    If gemColor = Gem.colors.yellow Or gemColor = Gem.colors.orange Or gemColor = Gem.colors.green Or gemColor = Gem.colors.prismatic Then
                        socketMatch = True
                    End If
                Case Item.colors.blue
                    If gemColor = Gem.colors.blue Or gemColor = Gem.colors.green Or gemColor = Gem.colors.purple Or gemColor = Gem.colors.prismatic Then
                        socketMatch = True
                    End If
                Case Item.colors.prismatic
                    socketMatch = True
            End Select
        Catch ex As Threading.ThreadAbortException
        Catch ex As Exception
            MessageBox.Show("An unexpected error has occurred. DK Optimize will exit.", "DK Optimize", MessageBoxButtons.OK, MessageBoxIcon.Stop, MessageBoxDefaultButton.Button1)
            ErrLogWriter.writeLog(ex)
            End
        End Try
    End Function

    Private Sub calculateInternalProcs()
        ' This function will calculate the base proc value for all internally
        ' modeled items.  Any cappable stat (ArP proc etc) will be modeled later.

        ' Values are pulled from data_internalprocs.xml in the user's program files directory
        ' This is a huge pain in the ass, but it's better than nothing and fairly flexible,
        ' so I'm okay with it. Note that this is only run at the very beginning of the thread,
        ' so some readability by sacrificing speed is probably worth it (thus items being done separately etc)

        ' Set up the file to be read.  Note that the file WILL exist at this point, or a huge error will
        ' occur.
        Try
            Dim m_xmld As XmlDocument
            Dim m_nodelist As XmlNodeList
            Dim m_node As XmlNode
            Dim buildinfo As FileVersionInfo = FileVersionInfo.GetVersionInfo(Windows.Forms.Application.ExecutablePath)

            m_xmld = New XmlDocument()
            m_xmld.Load(Windows.Forms.Application.CommonAppDataPath & "\data_internalprocs.xml")
            m_nodelist = m_xmld.SelectNodes("/internalprocs")
            m_node = m_nodelist(0)

            ' Bryntroll, the Bone Arbiter.
            ' Stored value is AP equivalence for the proc. No other effects.
            Select Case primaryTree
                Case "Blood"
                    BrynTrollNValue = weights.ap * Double.Parse(m_node.ChildNodes.Item(0).InnerText, System.Globalization.CultureInfo.InvariantCulture)
                Case "Unholy"
                    BrynTrollNValue = weights.ap * Double.Parse(m_node.ChildNodes.Item(1).InnerText, System.Globalization.CultureInfo.InvariantCulture)
                Case "Frost"
                    BrynTrollNValue = weights.ap * Double.Parse(m_node.ChildNodes.Item(2).InnerText, System.Globalization.CultureInfo.InvariantCulture)
            End Select

            ' Bryntroll, the Bone Arbiter (Heroic).
            ' Stored value is AP equivalence for the proc. No other effects.
            Select Case primaryTree
                Case "Blood"
                    BrynTrollHValue = weights.ap * Double.Parse(m_node.ChildNodes.Item(3).InnerText, System.Globalization.CultureInfo.InvariantCulture)
                Case "Unholy"
                    BrynTrollHValue = weights.ap * Double.Parse(m_node.ChildNodes.Item(4).InnerText, System.Globalization.CultureInfo.InvariantCulture)
                Case "Frost"
                    BrynTrollHValue = weights.ap * Double.Parse(m_node.ChildNodes.Item(5).InnerText, System.Globalization.CultureInfo.InvariantCulture)
            End Select

            ' Ashen Band of Unmatched / Endless Vengeance
            ' Stored value is a decimal representing total uptime. No other effects.
            AshenRingEValue = 480 * weights.ap * Double.Parse(m_node.ChildNodes.Item(30).InnerText, System.Globalization.CultureInfo.InvariantCulture)

            ' Shadowmourne
            ' Stored value is both a constant amount of Strength representing the stacking buff
            ' and an Attack Power equivalent per-spec value for the damage proc.
            ShadowmourneValue = weights.str * Double.Parse(m_node.ChildNodes.Item(31).InnerText, System.Globalization.CultureInfo.InvariantCulture)
            Select Case primaryTree
                Case "Blood"
                    ShadowmourneValue += (weights.ap * Double.Parse(m_node.ChildNodes.Item(32).InnerText, System.Globalization.CultureInfo.InvariantCulture))
                Case "Unholy"
                    ShadowmourneValue += (weights.ap * Double.Parse(m_node.ChildNodes.Item(33).InnerText, System.Globalization.CultureInfo.InvariantCulture))
                Case "Frost"
                    ShadowmourneValue += (weights.ap * Double.Parse(m_node.ChildNodes.Item(34).InnerText, System.Globalization.CultureInfo.InvariantCulture))
            End Select

            ' Deathbringer's Will
            ' Stored value gives three stats and a total uptime.
            ' Add non-ArP stats here and then flag to add ArP at the same time as other trinkets.
            Dim statIndex(0 To 2) As Integer
            Dim loopvar As Integer
            Select Case primaryTree
                Case "Blood"
                    statIndex(0) = Double.Parse(m_node.ChildNodes.Item(6).InnerText, System.Globalization.CultureInfo.InvariantCulture)
                    statIndex(1) = Double.Parse(m_node.ChildNodes.Item(7).InnerText, System.Globalization.CultureInfo.InvariantCulture)
                    statIndex(2) = Double.Parse(m_node.ChildNodes.Item(8).InnerText, System.Globalization.CultureInfo.InvariantCulture)
                    DeathbringersWillNUptime = (Double.Parse(m_node.ChildNodes.Item(9).InnerText, System.Globalization.CultureInfo.InvariantCulture))
                Case "Unholy"
                    statIndex(0) = Double.Parse(m_node.ChildNodes.Item(10).InnerText, System.Globalization.CultureInfo.InvariantCulture)
                    statIndex(1) = Double.Parse(m_node.ChildNodes.Item(11).InnerText, System.Globalization.CultureInfo.InvariantCulture)
                    statIndex(2) = Double.Parse(m_node.ChildNodes.Item(12).InnerText, System.Globalization.CultureInfo.InvariantCulture)
                    DeathbringersWillNUptime = (Double.Parse(m_node.ChildNodes.Item(13).InnerText, System.Globalization.CultureInfo.InvariantCulture))
                Case "Frost"
                    statIndex(0) = Double.Parse(m_node.ChildNodes.Item(14).InnerText, System.Globalization.CultureInfo.InvariantCulture)
                    statIndex(1) = Double.Parse(m_node.ChildNodes.Item(15).InnerText, System.Globalization.CultureInfo.InvariantCulture)
                    statIndex(2) = Double.Parse(m_node.ChildNodes.Item(16).InnerText, System.Globalization.CultureInfo.InvariantCulture)
                    DeathbringersWillNUptime = (Double.Parse(m_node.ChildNodes.Item(17).InnerText, System.Globalization.CultureInfo.InvariantCulture))
            End Select
            DeathbringersWillNValue = 0
            DeathbringersWillNArP = False
            For loopvar = 0 To 2
                Select Case statIndex(loopvar)
                    Case 0
                        ' None
                    Case 1
                        ' Strength
                        DeathbringersWillNValue += 600 * weights.str * (DeathbringersWillNUptime / 3)
                    Case 2
                        ' AP
                        DeathbringersWillNValue += 1200 * weights.ap * (DeathbringersWillNUptime / 3)
                    Case 3
                        ' Crit
                        DeathbringersWillNValue += 600 * weights.crit * (DeathbringersWillNUptime / 3)
                    Case 4
                        ' Haste
                        DeathbringersWillNValue += 600 * weights.haste * (DeathbringersWillNUptime / 3)
                    Case 5
                        'ArP
                        DeathbringersWillNArP = True
                    Case 6
                        'Agi
                        DeathbringersWillNValue += 600 * weights.agi * (DeathbringersWillNUptime / 3)
                End Select
            Next

            ' Deathbringer's Will (Heroic)
            ' Stored value gives three stats and a total uptime.
            ' Add non-ArP stats here and then flag to add ArP at the same time as other trinkets.
            Select Case primaryTree
                Case "Blood"
                    statIndex(0) = Double.Parse(m_node.ChildNodes.Item(18).InnerText, System.Globalization.CultureInfo.InvariantCulture)
                    statIndex(1) = Double.Parse(m_node.ChildNodes.Item(19).InnerText, System.Globalization.CultureInfo.InvariantCulture)
                    statIndex(2) = Double.Parse(m_node.ChildNodes.Item(20).InnerText, System.Globalization.CultureInfo.InvariantCulture)
                    DeathbringersWillHUptime = (Double.Parse(m_node.ChildNodes.Item(21).InnerText, System.Globalization.CultureInfo.InvariantCulture))
                Case "Unholy"
                    statIndex(0) = Double.Parse(m_node.ChildNodes.Item(22).InnerText, System.Globalization.CultureInfo.InvariantCulture)
                    statIndex(1) = Double.Parse(m_node.ChildNodes.Item(23).InnerText, System.Globalization.CultureInfo.InvariantCulture)
                    statIndex(2) = Double.Parse(m_node.ChildNodes.Item(24).InnerText, System.Globalization.CultureInfo.InvariantCulture)
                    DeathbringersWillHUptime = (Double.Parse(m_node.ChildNodes.Item(25).InnerText, System.Globalization.CultureInfo.InvariantCulture))
                Case "Frost"
                    statIndex(0) = Double.Parse(m_node.ChildNodes.Item(26).InnerText, System.Globalization.CultureInfo.InvariantCulture)
                    statIndex(1) = Double.Parse(m_node.ChildNodes.Item(27).InnerText, System.Globalization.CultureInfo.InvariantCulture)
                    statIndex(2) = Double.Parse(m_node.ChildNodes.Item(28).InnerText, System.Globalization.CultureInfo.InvariantCulture)
                    DeathbringersWillHUptime = (Double.Parse(m_node.ChildNodes.Item(29).InnerText, System.Globalization.CultureInfo.InvariantCulture))
            End Select
            DeathbringersWillHValue = 0
            DeathbringersWillHArP = False
            For loopvar = 0 To 2
                Select Case statIndex(loopvar)
                    Case 0
                        ' None
                    Case 1
                        ' Strength
                        DeathbringersWillHValue += 700 * weights.str * (DeathbringersWillNUptime / 3)
                    Case 2
                        ' AP
                        DeathbringersWillHValue += 1400 * weights.ap * (DeathbringersWillNUptime / 3)
                    Case 3
                        ' Crit
                        DeathbringersWillHValue += 700 * weights.crit * (DeathbringersWillNUptime / 3)
                    Case 4
                        ' Haste
                        DeathbringersWillHValue += 700 * weights.haste * (DeathbringersWillNUptime / 3)
                    Case 5
                        'ArP
                        DeathbringersWillHArP = True
                    Case 6
                        'Agi
                        DeathbringersWillHValue += 700 * weights.agi * (DeathbringersWillNUptime / 3)
                End Select
            Next
        Catch ex As Threading.ThreadAbortException
        Catch ex As Exception
            MessageBox.Show("An unexpected error has occurred. DK Optimize will exit.", "DK Optimize", MessageBoxButtons.OK, MessageBoxIcon.Stop, MessageBoxDefaultButton.Button1)
            ErrLogWriter.writeLog(ex)
            End
        End Try
    End Sub

End Class
